package io.github.openfeign.spring.enhancer;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.Opcode;
import lombok.extern.slf4j.Slf4j;

/**
 * Feign请求模板增强转换器
 *
 * <h3>核心功能：</h3>
 * <ul>
 *   <li>为 {@code feign.RequestTemplate} 增加延迟输出控制能力</li>
 *   <li>通过字节码增强实现以下改造：
 *     <ol>
 *       <li>添加私有字段：{@code private Consumer delayOutput}</li>
 *       <li>添加字段访问方法：getter/setter</li>
 *       <li>在 {@code from()} 方法返回前注入输出流设置逻辑</li>
 *     </ol>
 *   </li>
 * </ul>
 *
 * <h3>字节码修改位置示例：</h3>
 * <pre>
 * 原始字节码：
 *   106: invokeinterface #135, 2      // Map.putAll
 *   111: aload_1
 *   112: areturn
 *
 * 修改后字节码：
 *   106: invokeinterface #135, 2
 *   111: aload_1
 *   112: aload_0
 *   113: invokevirtual #new_method_ref // 调用新增的setDelayOutput
 *   116: areturn
 * </pre>
 */
@Slf4j
public class RequestTemplateBytecodeTransformer extends AbstractBytecodeTransformer {


    public RequestTemplateBytecodeTransformer(final ClassPool classPool, final String classOutPath) {
        super(classPool, classOutPath);
    }

    /**
     * {@inheritDoc}
     */
    public void transformBytecode() throws NotFoundException, CannotCompileException, BadBytecode {
        final CtClass ctClass = classPool.get("feign.RequestTemplate");
        if(log.isDebugEnabled()){
            log.debug("Start transforming feign.RequestTemplate");
        }
        // 增加字段 delayOutput
        addFiledDelayOutput(ctClass);
        // 修改 from 方法
        updateMethodFrom(ctClass);
        redefineClassInMemory(ctClass);
        if(log.isDebugEnabled()){
            log.debug("Successfully transforming feign.RequestTemplate");
        }
    }

    private void updateMethodFrom(final CtClass ctClass) throws NotFoundException, CannotCompileException, BadBytecode {
        // 修改from方法
        final CtMethod method = ctClass.getDeclaredMethod("from", new CtClass[]{ctClass});

        // 2. 定位到需要修改的行，在行之前插入逻辑
        // Opcode.RETURN ||       // void 返回
        // Opcode.IRETURN ||      // int 返回
        // Opcode.ARETURN ||      // 对象返回
        // Opcode.LRETURN ||      // long 返回
        // Opcode.FRETURN ||      // float 返回
        // Opcode.DRETURN {      // double 返回
        insertBeforeFinalReturn(method, Opcode.ARETURN, "template.setDelayOutput($1.delayOutput);");
    }


    private void addFiledDelayOutput(final CtClass ctClass) throws NotFoundException, CannotCompileException {
        // 添加outStream字段
        final CtField outStreamField = new CtField(classPool.get("java.util.function.Consumer"), "delayOutput", ctClass);
        outStreamField.setModifiers(Modifier.PRIVATE);
        ctClass.addField(outStreamField);

        final CtMethod setter = new CtMethod(
                ctClass,
                "setDelayOutput",
                new CtClass[]{classPool.get("java.util.function.Consumer")},
                ctClass
        );
        setter.setModifiers(Modifier.PUBLIC);
        setter.setBody("{ $0.delayOutput = $1; $0.bodyTemplate = null; return $0; }");
        ctClass.addMethod(setter);

        // 2. 创建新方法
        CtMethod getter = CtNewMethod.make(
                "public java.util.function.Consumer getDelayOutput() {\n" +
                        "   return $0.delayOutput;\n" +
                        "}\n",
                ctClass
        );
        ctClass.addMethod(getter);
    }
}
